package com.kingbook.core.auth;

import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.kingbook.common.cache.JedisUtils;
import com.kingbook.common.enums.TokenType;
import com.kingbook.common.security.CryptosManager;
import com.kingbook.common.thread.ThreadLocalManager;
import com.kingbook.common.utils.StringUtils;
import com.kingbook.core.result.JSONResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;
import java.util.Set;

import static com.kingbook.core.auth.LoginManager.DeviceType.MANY;
import static com.kingbook.core.auth.LoginManager.DeviceType.ONE;

@Slf4j
@Component
public class LoginManagerBak {

    public final static String AUTH = "Authorization";
    public final static String SIGN = "sign";
    public final static String REQUEST_ID = "requestId";
    private final static String MID = "mid";
    private final static String LOGIN_TOKEN = "loginToken";
    private final static String ACCESS_TOKEN = "accessToken";
    private final static String ACCESS_TOKEN2 = "accessTokenOld";
    private final static String ACCESS_TIME = "accessTime";

    private static int sessionLoginTimeOut;

    private static int sessionAccessTimeOut;

    private final static String SESSION_MANAGER = "SESSION_MANAGER";

    private static TokenType tokenType = TokenType.NUM;

    private static DeviceType deviceType;

    enum DeviceType {
        ONE, MANY
    }

//    @Value("${login.deviceType}")
//    public void setLoginType(String type) {
//        deviceType = MANY.name().equalsIgnoreCase(type) ? MANY : ONE;
//    }

    @Value("${login.sessionLoginTimeOut:30}")
    public void setSessionLoginTimeOut(Integer days) {
        sessionLoginTimeOut = days * 1440 * 60;
    }

    @Value("${login.sessionAccessTimeOut:5}")
    public void setSessionAccessTimeOut(Integer mins) {
        sessionAccessTimeOut = mins * 6;
    }


    /**
     * 检查是否登录，如果登录返回访问令牌（每次生成新TOKEN）
     * 如果同时页面发起三次请求，使用同一个token，则在第一个请求验证后返回新token时，其他两个请求的旧token应该支持验证
     *
     * @param auth 访问认证
     * @return 返回登录信息
     */
    public static JSONResult checkLogin(String requestId, String headerSign, String auth) {
        try {
            String[] auths = decipherTea(auth);
            if (ArrayUtils.isEmpty(auths)) {
                return JSONResult.failed("Decipher Authorization failed");
            }
            String mid = auths[0];
            String loginToken = auths[1];
            String accessToken = auths[2];
            String sign = auths[3];
            if (!sign.equals(headerSign)) {
                return JSONResult.failed("sign check failed");
            }

            log.debug("----->[{}] Auth dec: sign={} loginToken={} mid={} accessToken={} ", requestId, sign, loginToken, mid, accessToken);
            synchronized (AUTH) {
                String key = choiceKey(mid, loginToken);
                String value = JedisUtils.get(key);
                if (StringUtils.isNotEmpty(value)) {
                    JSONObject json = JSONObject.parseObject(value);
                    log.debug("----->[{}] Cache: {} ", requestId, json.toJSONString());
                    String lt = json.getString(LOGIN_TOKEN);
                    String at = json.getString(ACCESS_TOKEN);
                    if (loginToken.equals(lt) && accessToken.equals(at)) {
                        if (json.getLong(ACCESS_TIME) + sessionAccessTimeOut * 1000 < System.currentTimeMillis()) {
                            // 如果当前用户key在重置，后面的key就不需要重置newAccessToken
                            ThreadLocalManager.set(key, true);
                            String newAccessToken = TokenFactory.genToken(tokenType);
                            json.put(ACCESS_TOKEN, newAccessToken);
                            json.put(ACCESS_TOKEN2, accessToken);
                            json.put(ACCESS_TIME, System.currentTimeMillis());
                            auth = encoderTea(mid, loginToken, newAccessToken, sign);
                            log.debug("----->[{}] Create AccessToken: mid={} accessToken={} accessTokenOld={} auth={} ", requestId, mid, newAccessToken, accessToken, auth);
                            JedisUtils.set(key, json.toJSONString(), sessionLoginTimeOut);
                        }
                        return JSONResult.success(auth);
                    }
                    // 第一个auth生成新的之后，后续的请求用老的auth，在超时时间内允许通过
                    if (loginToken.equals(lt) && accessToken.equals(json.getString(ACCESS_TOKEN2))) {
                        auth = encoderTea(mid, loginToken, at, sign);
                        log.debug("----->[{}] ACCESS_TOKEN2: mid={} accessToken={} accessTokenOld={} auth={} ", requestId, mid, at, accessToken, auth);
                        if (json.getLong(ACCESS_TIME) + sessionAccessTimeOut * 1000 < System.currentTimeMillis()) {
                            json.put(ACCESS_TOKEN2, "");
                            JedisUtils.set(key, json.toJSONString(), sessionLoginTimeOut);
                        }
                        return JSONResult.success(auth);
                    }
                }
            }
        } catch (Exception e) {
            log.error("ERROR checkLogin :{} {}", auth, e.getMessage());
            return JSONResult.failed("Check Authorization ERROR");
        }
        return JSONResult.failed("Check Authorization failed");
    }

    /**
     * 用户登录，并返回AUTH
     */
    public static JSONResult doLogin(String mid, String sign) {
        String loginToken = TokenFactory.genToken(tokenType);
        String accessToken = TokenFactory.genToken(tokenType);
        JSONObject json = new JSONObject();
        json.put(MID, mid);
        json.put(SIGN, sign);
        json.put(LOGIN_TOKEN, loginToken);
        json.put(ACCESS_TOKEN, accessToken);
        json.put(ACCESS_TIME, System.currentTimeMillis());
        String key = choiceKey(mid, loginToken);
        if (JedisUtils.exists(key)) {
            JedisUtils.del(key);
        }
        //存取的key要一致
        JedisUtils.set(key, json.toJSONString(), sessionLoginTimeOut);
        log.debug("登录成功: mid={},key={}", mid, key);
        if (MANY.equals(deviceType)) {
            //多设备登录模式，需要记录 SESSION_MANAGER，以便修改密码时删除其他设备的登录和删除过去的loginToken
            Set<String> loginTokens = Sets.newHashSet();
            loginTokens.add(loginToken);
            List<Set> list = JedisUtils.getMapByKey(SESSION_MANAGER, Set.class, mid);
            if (list != null && list.size() > 0) {
                Set t = list.get(0);
                for (Object k : t) {
                    if (JedisUtils.exists(choiceKey(mid, (String) k))) {
                        loginTokens.add((String) k);
                    }
                }
            }
            Map<String, Set<String>> map = Maps.newHashMap();
            map.put(mid, loginTokens);
            JedisUtils.setMap(SESSION_MANAGER, map);
        }
        json.clear();
        json.put(AUTH, encoderTea(mid, loginToken, accessToken, sign));
        return JSONResult.success(json);
    }

    public static JSONResult logout(String auth) {
        String[] auths = decipherTea(auth);
        if (auths != null) {
            String key = choiceKey(auths[0], auths[1]);
            JedisUtils.del(key);
        }
        return JSONResult.success("退出成功");
    }

    public static int getMid(String auth) {
        String[] d = decipherTea(auth);
        if (d != null && d.length > 0)
            return Integer.parseInt(d[0]);
        return 1;
    }

    public static String[] decipherTea(String auth) {
        if (StringUtils.isNotEmpty(auth)) {
            return CryptosManager.teaDecipher(auth, TokenFactory.teaKey).split("-");
        }
        return null;
    }

    public static String encoderTea(Object... o) {
        StringBuilder sb = new StringBuilder();
        for (Object ob : o) {
            sb.append(ob.toString()).append("-");
        }
        return CryptosManager.teaEncipher(sb.toString(), TokenFactory.teaKey);
    }

    /**
     * key = userCode    一个账户只能同时在一个设备登录
     * key = loginToken  一个账户可以在多个设备登录，如果用户不退出，就无法剔除以前的登录信息，只能通过超时清除
     */
    private static String choiceKey(String userCode, String loginToken) {
        switch (deviceType) {
            case ONE:
                return "SESSION:" + userCode;
            default:
                return "SESSION:" + userCode + ":" + loginToken;
        }
    }

}
